只要按住滑鼠就能在螢幕上畫畫, 線條大小, 顏色都會隨著畫筆移動的距離漸變. 像這樣
<canvas>
<canvas>
標籤是HTML5新增的元素, 透過<canvas>
我們能夠以JS的程式碼輕易實現在瀏覽器繪圖, 製作動畫及影像處理等功能.
首先建立<canvas>
標籤. canvas
元素就像一塊作畫空間, width
, height
等屬性則用以初始化畫布大小. HTML程式碼如下:
<canvas id="draw" width="400" height="320"></canvas>
在JS裡面, 我們可以用canvas.width
, canvas.height
來存取width, height兩個屬性, 用來改變畫布大小.
這裡的canvas.width
, 和canvas.style.width
是不同的東西. canvas.width
指的是畫布內實際有多少像素寬, 如果canvas.width="400px"
, canvas.style.width="800px"
, 會產生一個像素解析度只有400px但體積占800px的畫布空間.
<canvas>
元素內建getContext('2d)
物件, 用以創造出渲染環境(context). 可以想像成小畫家的程式(canvas)打開後, 會看到畫布本體及其提供的作畫工具. 參數可設置為2d
或3d
, 在這裡我們只需平面作畫, 將參數設為2d
. JS程式碼如下:
const canvas = document.querySelector('#draw');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
到此, 建立了一個畫布長寬與瀏覽器視窗相同的全屏畫布, 渲染環境為平面. 與平面作畫相關的工具都在getContext('2d')
物件內. 因此特別將其存為一個變數, 方便我們呼喚渲染工具.
要讓滑鼠拖曳時就會畫畫, 需要建立監聽器. 架構大致如下:
// 初始化 isDrawing
let isDrawing = false;
function draw() {
if(!isDrawing) return;
// 畫畫
}
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
});
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', () => isDrawing = false);
canvas.addEventListener('mouseout', () => isDrawing = false);
首先分解一下'拖曳'這個動作, 拖曳就是'滑鼠有按'且'滑鼠有動', 也就是mousedown
和mousemove
.
用isDrawing
代表是否正在畫畫, 觸發mousedown
時將之設為true
, 滑鼠按鍵放開(mouseup
)或滑鼠移出元素範圍(mouseout
), 將之設為false
.
只要在滑鼠移動時(mousemove
)觸發的回呼函式draw()
中先判斷isDrawing
是否為true
, 再做動作, 就能達到'拖曳時就動作'的效果.
接下來是畫畫的函式了, 剛說到畫畫的工具都放在getContext('2d')內, 可以想成小畫家的畫筆, 在下筆前需要先選好筆的形狀, 線條粗細, 顏色等... 程式碼如下
// 初始化下筆點
let [lastX, lastY] = [0, 0];
function draw() {
// 設定線條色彩
ctx.strokeStyle = `red`;
// 開始設定路徑
ctx.beginPath();
// 將畫筆移動到下筆座標點
ctx.moveTo(lastX, lastY);
// 從目前畫筆位置畫線到新座標點 (滑鼠所在位置)
ctx.lineTo(e.offsetX, e.offsetY);
// 上面都是在規劃路徑, 這裡才是將線畫出來
ctx.stroke();
}
首先會看到所有功能都是附屬在getContext(‘2d)
物件下的方法. strokeStyle
用來決定畫線的顏色. beginPath()
就是開始規劃路徑, 等路徑規劃完後, 用stroke()
實際將線條路徑渲染出來.
moveTo()
, lineTo()
都是規劃路徑的工具, 前者為移動下筆座標點, 預設的下筆座標點為原點. 後者為從下筆點畫線到某個新的座標點.
順帶一提, <canvas>
使用的是原點在左上, x軸向右為正, y軸向下為正的直角座標系.
照著上述程式碼, 只能不斷畫出從原點到座標位置的線而已. 雖然很漂亮, 但不是我們要的. 我們要的是一條會跟著畫筆軌跡移動的線.
一條任意形狀的線, 可以想成是非常多極小長度的直線所組成的. 這就是微分的概念, 也是我們下筆時能畫出任意線的概念.
// 初始化下筆點
let [lastX, lastY] = [0, 0];
function draw() {
// ... 前略
ctx.stroke();
// 每次畫完後更新下筆點
[lastX, lastY] = [e.offsetX, e.offsetY];
}
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
// 每次滑鼠按下時先將下筆點移到畫筆位置
[lastX, lastY] = [e.offsetX, e.offsetY];
});
moveTo(lastX, lastY)
的兩個參數代表下筆起始位置, 只要在每次下筆前先將起始位置移到畫筆位置, 每次畫畫就一定會由畫筆處開始.
由於draw()
回呼函數是藉由監聽到mousemove
而觸發的, 只要在畫完圖後將下筆位置更新成目前滑鼠所在位置, 下筆位置就會隨著滑鼠移動不斷更新, 按下滑鼠並移動就會畫出一堆極短但彼此連接的線, 總體看起來就是一條任意形狀的長線!
不過此時的線看起來不太圓滑, 我們可以在函數外初始化一些畫筆的設定, 讓線接起來圓滑一點.
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = 20;
lineJoin
是線跟線的接點處的樣式, lineCap
是線的終點處的形狀, 設成round
, 在線接合或終止時看起來比較順. lineWidth
則控制了線的寬度, 一般建議設成偶數.
接下來我們希望讓畫出來的線條隨著移動而變色, 這次我們用hsl()
來指定線條顏色. hsl()
只是表示顏色的另一種方式, HSL分別代表'Hue'(顏色), 'Saturation(飽和度)', 'Lightness(亮度)'.
使用hsl()
的好處在於, 第一個參數hue
代表顏色, 範圍數值從0 ~ 360, 是循環的, 0 就是 360 就是紅色. 要讓畫筆顏色循環, 只需要將strokeStyle
修正一下:
// 宣告並初始化hue
let hue = 0;
function draw() {
// ...前略
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
// 讓hue隨著每次呼叫函式而累加
hue++;
if(hue >= 360) {hue = 0}
}
最後我們希望畫筆大小會隨著距離而變. 和hue的概念類似. 可以在hue下面接:
// direction決定畫筆大小的變化方向, 變大或變小
let direction = true;
function draw() {
// ... 前略
hue++;
if(hue >= 360) {hue = 0}
// 隨著畫筆移動, 持續變大或變小
if(direction) {
ctx.lineWidth++;
} else {
ctx.lineWidth--;
}
// 超過特定範圍時修正畫筆大小的改變方向
if(ctx.lineWidth >= 50 || ctx.lineWidth <= 10) {
direction = !direction;
}
}
用direction
控制畫筆變大或變小, 並隨著draw()
被呼叫而改變逐漸往該方向變動尺寸.
到此任務完成!
以上就是JS30 第八天!